001 /*
002 * Copyright 2004-2005 Stephen J. McConnell.
003 * Copyright 2004 Niclas Hedhman.
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
014 * implied.
015 *
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 */
019
020 package net.dpml.transit;
021
022 import java.io.Serializable;
023 import java.net.URLStreamHandler;
024
025 import java.net.MalformedURLException;
026 import java.net.URI;
027 import java.net.URISyntaxException;
028 import java.net.URL;
029
030 /**
031 * A utility class the handles validation of <code>artifact</code> style uri
032 * strings.
033 *
034 * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
035 * @version 1.0.1
036 */
037 public final class Artifact implements Serializable, Comparable
038 {
039 /**
040 * Constant scheme name for the artifact protocol.
041 */
042 public static final String ARTIFACT = "artifact";
043
044 /**
045 * Constant scheme name for the link protocol.
046 */
047 public static final String LINK = "link";
048
049 /**
050 * Constant scheme name for the local protocol.
051 */
052 public static final String LOCAL = "local";
053
054 static final long serialVersionUID = 1L;
055
056 // ------------------------------------------------------------------------
057 // static
058 // ------------------------------------------------------------------------
059
060 /**
061 * Creation of a new artifact instance using a supplied uri specification.
062 * An artifact uri contains the protocol identifier, a type, a group
063 * designator, a name, and an optional version identifier.
064 * <p>The following represent valid artifact uri examples:</p>
065 *
066 * <ul>
067 * <li>artifact:jar:dpml/transit/dpml-transit-main#1234</li>
068 * <li>artifact:jar:dpml/transit/dpml-transit-main</li>
069 * <li>link:jar:dpml/transit/dpml-transit-main#1.0</li>
070 * </ul>
071 *
072 * <p>
073 * If there is a internal reference identifier which is marked by the
074 * exclamation mark followed by slash (!/) it will be stripped. The
075 * version part can be either before or after such identifier. Example;
076 * <pre>
077 * artifact:war:jmx-html/jmx-html#1.3!/images/abc.png
078 * artifact:war:jmx-html/jmx-html!/images/abc.png#1.3
079 * </pre>
080 * The above uris will both be referencing
081 * <code>artifact:war:jmx-html/jmx-html#1.3</code>
082 * </p>
083 * @param uri the artifact uri
084 * @return the new artifact
085 * @exception java.net.URISyntaxException if the supplied uri is not valid.
086 * @exception UnsupportedSchemeException if the URI does not have "artifact"
087 * or "link" as its <strong>scheme</strong>.
088 */
089 public static final Artifact createArtifact( String uri )
090 throws URISyntaxException, UnsupportedSchemeException
091 {
092 if( null == uri )
093 {
094 throw new NullArgumentException( "uri" );
095 }
096 int asterix = uri.indexOf( "!" );
097 if( asterix == -1 )
098 {
099 return createArtifact( new URI( uri ) );
100 }
101 else
102 {
103 String path = uri.substring( 0, asterix );
104 int versionPos = uri.indexOf( "#" );
105 if( versionPos < asterix )
106 {
107 return createArtifact( path );
108 }
109 else
110 {
111 path = path + uri.substring( versionPos );
112 return createArtifact( path );
113 }
114 }
115 }
116
117 /**
118 * Creation of a new artifact instance using a supplied uri specification. An
119 * artifact uri contains the protocol identifier, an optional type, a group
120 * designator, a name, and an optional version identifier.
121 * <p>The following represent valid artifact uri examples:</p>
122 *
123 * <ul>
124 * <li>artifact:jar:metro/cache/dpml-cache-main#1.0.0</li>
125 * <li>artifact:metro/cache/dpml-cache-main#1.0.0</li>
126 * <li>artifact:metro/cache/dpml-cache-main</li>
127 * </ul>
128 *
129 * @param uri the artifact uri
130 * @return the new artifact
131 * @exception UnsupportedSchemeException if the URI does not have "artifact"
132 * or "link" as its <strong>scheme</strong>.
133 */
134 public static final Artifact createArtifact( URI uri )
135 throws UnsupportedSchemeException
136 {
137 if( null == uri )
138 {
139 throw new NullArgumentException( "uri" );
140 }
141 String scheme = uri.getScheme();
142 if( null == scheme )
143 {
144 final String error =
145 "URI does not declare a scheme: " + uri;
146 throw new UnsupportedSchemeException( error );
147 }
148 //if( !scheme.equals( ARTIFACT ) && !scheme.equals( LINK ) && !scheme.equals( LOCAL ) )
149 //{
150 // final String error =
151 // "URI contains a scheme that is not recognized: " + uri;
152 // throw new UnsupportedSchemeException( error );
153 //}
154 return new Artifact( uri );
155 }
156
157 /**
158 * Creation of a new artifact instance using a supplied group, name,
159 * version and type arguments.
160 *
161 * @param group the artifact group identifier
162 * @param name the artifact name
163 * @param version the version
164 * @param type the type
165 * @return the new artifact
166 * @exception NullArgumentException if any of the <code>group</code>,
167 * <code>name</code> or <code>type</code> arguments are
168 * <code>null</code>.
169 */
170 public static Artifact createArtifact( String group, String name, String version, String type )
171 throws NullArgumentException
172 {
173 return createArtifact( ARTIFACT, group, name, version, type );
174 }
175
176 /**
177 * Creation of a new artifact instance using a supplied group, name,
178 * version and type arguments.
179 *
180 * @param scheme the artifact scheme
181 * @param group the artifact group identifier
182 * @param name the artifact name
183 * @param version the version
184 * @param type the type
185 * @return the new artifact
186 * @exception NullArgumentException if any of the <code>group</code>,
187 * <code>name</code> or <code>type</code> arguments are
188 * <code>null</code>.
189 */
190 public static Artifact createArtifact( String scheme, String group, String name, String version, String type )
191 throws NullArgumentException
192 {
193 if( name == null )
194 {
195 throw new NullArgumentException( "name" );
196 }
197 if( type == null )
198 {
199 throw new NullArgumentException( "type" );
200 }
201 if( scheme == null )
202 {
203 throw new NullArgumentException( "scheme" );
204 }
205 String composite = buildComposite( scheme, group, name, version, type );
206 try
207 {
208 URI uri = new URI( composite );
209 return new Artifact( uri );
210 }
211 catch( URISyntaxException e )
212 {
213 // Can not happen.
214 final String error =
215 "An internal error has occurred. "
216 + "The following URI could not be constructed: " + composite;
217 throw new TransitRuntimeException( error );
218 }
219 }
220
221 private static String buildComposite( String scheme, String group, String name, String version, String type )
222 {
223 if( null == group )
224 {
225 if( null == version )
226 {
227 return scheme + ":" + type + ":" + name;
228 }
229 else
230 {
231 return scheme + ":" + type + ":" + name + "#" + version;
232 }
233 }
234 else
235 {
236 if( null == version )
237 {
238 return scheme + ":" + type + ":" + group + "/" + name;
239 }
240 else
241 {
242 return scheme + ":" + type + ":" + group + "/" + name + "#" + version;
243 }
244 }
245 }
246
247 /**
248 * Construct a new URL form a given URI. If the URI is a Transit URI the
249 * returned URL will be associated with the appropriate handler.
250 * @param uri the uri to convert
251 * @return the converted url
252 * @exception MalformedURLException if the url could not be created
253 */
254 public static URL toURL( URI uri ) throws MalformedURLException
255 {
256 try
257 {
258 Artifact artifact = Artifact.createArtifact( uri );
259 return artifact.toURL();
260 }
261 catch( UnsupportedSchemeException e )
262 {
263 }
264 catch( IllegalArgumentException e )
265 {
266 }
267
268 try
269 {
270 return uri.toURL();
271 }
272 catch( MalformedURLException mue )
273 {
274 throw mue;
275 }
276 catch( Throwable t )
277 {
278 final String error =
279 "Unexpected error while attempting to convert a uri to a url."
280 + "\n URI: "
281 + uri;
282 throw new TransitRuntimeException( error, t );
283 }
284 }
285
286 /**
287 * Test if the supplied uri is from the artifact family. Specificially
288 * the test validates that the supplied uri has a scheme corresponding to
289 * 'artifact', link', or 'local'.
290 * @param uri the uri to check
291 * @return true if thie uri is artifact based
292 */
293 public static boolean isRecognized( URI uri )
294 {
295 String scheme = uri.getScheme();
296 if( ARTIFACT.equals( scheme ) )
297 {
298 return true;
299 }
300 else if( LINK.equals( scheme ) )
301 {
302 return true;
303 }
304 else
305 {
306 return LOCAL.equals( scheme );
307 }
308 }
309
310 // ------------------------------------------------------------------------
311 // state
312 // ------------------------------------------------------------------------
313
314 /**
315 * The artifact uri.
316 */
317 private final URI m_uri;
318
319 /**
320 * The artifact group.
321 */
322 private final String m_group;
323
324 /**
325 * The artifact name.
326 */
327 private final String m_name;
328
329 /**
330 * The artifact type.
331 */
332 private final String m_type;
333
334 // ------------------------------------------------------------------------
335 // constructor
336 // ------------------------------------------------------------------------
337
338 /**
339 * Creation of a new Artifact using a supplied uri.
340 * @param uri a uri of the form [scheme]:[type]:[group]/[name]#[version]
341 * where [scheme] is one of 'link', 'artifact' or 'local'.
342 */
343 private Artifact( URI uri )
344 throws IllegalArgumentException
345 {
346 m_uri = uri;
347 String ssp = uri.getSchemeSpecificPart();
348
349 if( ssp.indexOf( "//" ) > -1
350 || ssp.indexOf( ":/" ) > -1
351 || ssp.endsWith( "/" ) )
352 {
353 final String error =
354 "Invalid character sequence in uri ["
355 + uri + "].";
356 throw new IllegalArgumentException( error );
357 }
358
359 int typeIndex = ssp.indexOf( ':' );
360 if( typeIndex > -1 )
361 {
362 String type = ssp.substring( 0, typeIndex );
363 m_type = type;
364 ssp = ssp.substring( typeIndex + 1 );
365 }
366 else
367 {
368 final String error = "Supplied artifact specification ["
369 + uri + "] does not contain a type.";
370 throw new IllegalArgumentException( error );
371 }
372
373 // ssp now contains group, name and version
374
375 int groupIndex = ssp.lastIndexOf( '/' );
376 if( groupIndex > -1 )
377 {
378 String group = ssp.substring( 0, groupIndex );
379 m_group = group;
380 m_name = ssp.substring( groupIndex + 1 );
381 }
382 else
383 {
384 m_group = null;
385 m_name = ssp;
386 }
387
388 String ver = uri.getFragment();
389 if( ver != null )
390 {
391 if( ver.indexOf( '/' ) >= 0
392 || ver.indexOf( '%' ) >= 0
393 || ver.indexOf( '\\' ) >= 0
394 || ver.indexOf( '*' ) >= 0
395 || ver.indexOf( '!' ) >= 0
396 || ver.indexOf( '(' ) >= 0
397 || ver.indexOf( '@' ) >= 0
398 || ver.indexOf( ')' ) >= 0
399 || ver.indexOf( '+' ) >= 0
400 || ver.indexOf( '\'' ) >= 0
401 || ver.indexOf( '{' ) >= 0
402 || ver.indexOf( '}' ) >= 0
403 || ver.indexOf( '[' ) >= 0
404 || ver.indexOf( '}' ) >= 0
405 || ver.indexOf( '?' ) >= 0
406 || ver.indexOf( ',' ) >= 0
407 || ver.indexOf( '#' ) >= 0
408 || ver.indexOf( '=' ) >= 0
409 )
410 {
411 final String error =
412 "Supplied artifact specification ["
413 + uri
414 + "] contains illegal characters in the Version part.";
415 throw new IllegalArgumentException( error );
416 }
417 }
418 }
419
420 // ------------------------------------------------------------------------
421 // public
422 // ------------------------------------------------------------------------
423
424 /**
425 * Return the protocol for the artifact.
426 *
427 * @return the protocol scheme
428 */
429 public final String getScheme()
430 {
431 return m_uri.getScheme();
432 }
433
434 /**
435 * Return the group identifier for the artifact. The group identifier
436 * is composed of a sequence of named separated by the '/' character.
437 *
438 * @return the group identifier
439 */
440 public final String getGroup()
441 {
442 return m_group;
443 }
444
445 /**
446 * Return the name of the artifact.
447 *
448 * @return the artifact name
449 */
450 public final String getName()
451 {
452 return m_name;
453 }
454
455 /**
456 * Return the type of the artifact.
457 *
458 * @return the artifact type
459 */
460 public final String getType()
461 {
462 return m_type;
463 }
464
465 /**
466 * Return the posssibly null version identifier. The value of the version
467 * is an opaque string.
468 * @return the artifact version
469 */
470 public final String getVersion()
471 {
472 String ver = m_uri.getFragment();
473 if( ver == null )
474 {
475 return null;
476 }
477 else if( ver.length() == 0 )
478 {
479 return null;
480 }
481 else
482 {
483 return ver;
484 }
485 }
486
487 /**
488 * Test if the artifact scheme is recognized. Specificially
489 * the test validates that the artifact scheme corresponding to
490 * 'artifact', link', or 'local'.
491 * @return true if the uri scheme is recognized
492 */
493 public boolean isRecognized()
494 {
495 return isRecognized( m_uri );
496 }
497
498 /**
499 * Create an artifact url backed by the repository.
500 *
501 * @return the artifact url
502 */
503 public URL toURL()
504 {
505 String scheme = getScheme();
506 if( ARTIFACT.equals( scheme ) )
507 {
508 return toURL( new net.dpml.transit.artifact.Handler() );
509 }
510 else if( LINK.equals( scheme ) )
511 {
512 return toURL( new net.dpml.transit.link.Handler() );
513 }
514 else if( LOCAL.equals( scheme ) )
515 {
516 return toURL( new net.dpml.transit.local.Handler() );
517 }
518 else
519 {
520 final String error =
521 "URI scheme not recognized: " + m_uri;
522 throw new UnsupportedSchemeException( error );
523 }
524 }
525
526 /**
527 * Create an artifact url backed by the repository.
528 * @param handler the protocol handler
529 * @return the artifact url
530 */
531 public URL toURL( URLStreamHandler handler )
532 {
533 try
534 {
535 return new URL( null, m_uri.toASCIIString(), handler );
536 }
537 catch( MalformedURLException e )
538 {
539 // Can not happen!
540 final String error =
541 "An artifact URI could not be converted to a URL ["
542 + m_uri
543 + "].";
544 throw new TransitRuntimeException( error );
545 }
546 }
547
548 /**
549 * Create an artifact url backed by the repository.
550 *
551 * @return the artifact url
552 */
553 public URI toURI()
554 {
555 return m_uri;
556 }
557
558 // ------------------------------------------------------------------------
559 // Comparable
560 // ------------------------------------------------------------------------
561
562 /**
563 * Compare this artifact with another artifact. Artifact comparisom is
564 * based on a comparison of the string representation of the artifact with
565 * the string representation of the supplied object.
566 *
567 * @param object the object to compare with this instance
568 * @return the comparative order of the supplied object relative to this
569 * artifact
570 * @exception NullArgumentException if the supplied object argument is null.
571 * @exception ClassCastException if the supplied object is not an Artifact.
572 */
573 public int compareTo( Object object )
574 throws NullArgumentException, ClassCastException
575 {
576 if( object instanceof Artifact )
577 {
578 String name = this.toString();
579 return name.compareTo( object.toString() );
580 }
581 else if( null == object )
582 {
583 throw new NullArgumentException( "object" );
584 }
585 else
586 {
587 final String error =
588 "Object ["
589 + object.getClass().getName()
590 + "] does not implement ["
591 + this.getClass().getName() + "].";
592 throw new ClassCastException( error );
593 }
594 }
595
596 // ------------------------------------------------------------------------
597 // Object
598 // ------------------------------------------------------------------------
599
600 /**
601 * Return a string representation of the artifact.
602 * @return the artifact as a uri
603 */
604 public String toString()
605 {
606 return m_uri.toString();
607 }
608
609 /**
610 * Compare this artifact with the supplied object for equality. This method
611 * will return true if the supplied object is an Artifact and has an equal
612 * uri.
613 *
614 * @param other the object to compare with this instance
615 * @return TRUE if this artifact is equal to the supplied object
616 */
617 public boolean equals( Object other )
618 {
619 if( null == other )
620 {
621 return false;
622 }
623 else if( this == other )
624 {
625 return true;
626 }
627 else if( other instanceof Artifact )
628 {
629 Artifact art = (Artifact) other;
630 return m_uri.equals( art.m_uri );
631 }
632 else
633 {
634 return false;
635 }
636 }
637
638 /**
639 * Return the hashcode for the artifact.
640 * @return the hashcode value
641 */
642 public int hashCode()
643 {
644 return m_uri.hashCode();
645 }
646 }
647